package net.qiqbframework.config;

import net.qiqbframework.lifecycle.*;
import net.qiqbframework.common.utils.AnnotationUtil;
import net.qiqbframework.common.utils.ReflectionUtil;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nonnull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;

import static net.qiqbframework.common.utils.ReflectionUtil.invokeAndGetMethodValue;
import static java.lang.String.format;

@Slf4j
public abstract class LifecycleHandlerInspector {

    private static final String LIFECYCLE_PHASE_ATTRIBUTE_NAME = "phase";

    private LifecycleHandlerInspector() {
        // Utility class
    }

    public static void registerLifecycleHandlers(Configuration configuration, Object component) {
        if (component == null) {
            log.debug("Ignoring [null] component for inspection as it wont participate in the lifecycle");
            return;
        }
        if (component instanceof Lifecycle) {
            ((Lifecycle) component).registerLifecycleHandler(new LifecycleHandlerRegister() {
                @Override
                public void onStart(int phase, @Nonnull LifecycleHandler action) {
                    configuration.onStart(phase, action::handle);
                }

                @Override
                public void onShutdown(int phase, @Nonnull LifecycleHandler action) {
                    configuration.onShutdown(phase, action::handle);
                }
            });
        } else {
            registerLifecycleHandlers(configuration, component, StartHandler.class, Configuration::onStart);
            registerLifecycleHandlers(configuration, component, ShutdownHandler.class, LifecycleHandlerRegister::onShutdown);
        }
    }

    private static void registerLifecycleHandlers(Configuration configuration,
                                                  Object component,
                                                  Class<? extends Annotation> lifecycleAnnotation,
                                                  LifecycleRegistration registrationMethod) {
        for (Method method : ReflectionUtil.methodsOf(component.getClass())) {
            AnnotationUtil.findAnnotationAttributes(method, lifecycleAnnotation)
                    .ifPresent(lifecycleAnnotationAttributes -> {
                        if (method.getParameterCount() > 0) {
                            throw new ConfigurationException(format(
                                    "The @%s annotated method [%s] should not contain any parameters"
                                            + " as none are allowed on lifecycle handlers",
                                    lifecycleAnnotation.getSimpleName(), method
                            ));
                        }
                        int phase = (int) lifecycleAnnotationAttributes.get(LIFECYCLE_PHASE_ATTRIBUTE_NAME);
                        LifecycleHandler lifecycleHandler = () -> invokeAndReturn(
                                component, method, lifecycleAnnotation.getSimpleName(), phase
                        );
                        registrationMethod.registerLifecycleHandler(configuration, phase, lifecycleHandler);

                        log.debug(
                                "Found and registered a {} with phase [{}] from component [{}]",
                                lifecycleAnnotation.getSimpleName(), phase, component.getClass().getSimpleName()
                        );
                    });
        }
    }



    private static CompletableFuture<?> invokeAndReturn(Object lifecycleComponent,
                                                        Method lifecycleHandler,
                                                        String handlerType,
                                                        int phase) {
        try {
            log.debug(
                    "Invoking {} from component [{}] in phase [{}]",
                    handlerType, lifecycleComponent.getClass().getSimpleName(), phase
            );

            Object result = invokeAndGetMethodValue(lifecycleHandler, lifecycleComponent);

            return result instanceof CompletableFuture
                    ? (CompletableFuture<?>) result
                    : CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            CompletableFuture<Void> exceptionallyCompletedFuture = new CompletableFuture<>();
            exceptionallyCompletedFuture.completeExceptionally(
                    new LifecycleHandlerInvocationException(lifecycleHandler, lifecycleComponent, e)
            );
            return exceptionallyCompletedFuture;
        }
    }

    /**
     * Functional interface towards the registration of a {@code lifecycleHandler} on the given {@code phase} to the
     * {@link Configuration}.
     */
    @FunctionalInterface
    private interface LifecycleRegistration {

        void registerLifecycleHandler(Configuration configuration, int phase, LifecycleHandler lifecycleHandler);
    }
}
